using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Pool;
using MinesweeperPlus.Scripts.Abstractions.Interfaces;
using MinesweeperPlus.Scripts.Abstractions.Enums;
using MinesweeperPlus.Scripts.Configs;

namespace MinesweeperPlus.Scripts.Objects
{
    public sealed class Playfield
    {
        #region Constants
        private const float TOUCH_HOLD_TIME = 0.5f;
        #endregion

        #region Actions
        public Action OnCreateLevel;
        public Action OnClearLevel;
        public Action<EndPlayType> OnEndPlay;
        public Action OnFirstCellOpen;
        public Action<int> OnNumberOfUnsetFlagsChange;
        #endregion

        #region Variables
        private PlayfieldConfig _config;
        private PlayerInput _playerInput;
        private IObjectPool<Cell> _cellPool;
        private PlayerCamera _playerCamera;
        private bool _isFirstCellOpened;
        private int _numberOfTools;
        private GameData _gameData;
        private Vector3 _previousScreenPoint;
        private float _previousDistance;
        private bool _isTwoTouchStarted;

        private LevelConfig[] _levelConfigs => _config.LevelConfigs;
        #endregion
        #region Properties
        public ILevel CurrentLevel { get; private set; }
        public ILevel[] Levels { get; private set; }
        public PlayfieldToolType ToolType { get; set; }
        #endregion

        #region Playfield Methods
        public Playfield(
            PlayfieldConfig config, 
            PlayerCamera playerCamera, 
            PlayerInput playerInput, 
            GameData gameData)
        {
            _config = config;
            _playerInput = playerInput;
            _playerCamera = playerCamera;
            ToolType = PlayfieldToolType.Shovel;
            _numberOfTools = Enum.GetNames(typeof(PlayfieldToolType)).Length;
            _gameData = gameData;

            InitPools();
            InitLevels();

            _playerInput.OnStartFirstTouch += FirstTouchStarted;
            _playerInput.OnHoldFirstTouch += FirstTouchHolded;
            _playerInput.OnEndFirstTouch += FirstTouchEnded;
            
            _playerInput.OnStartSecondTouch += SecondTouchStarted;
            _playerInput.OnHoldSecondTouch += SecondTouchHolded;
            _playerInput.OnStartSecondTouch += SecondTouchEnded;
        }

        public void CreateLevel(ILevel level)
        {
            ToolType = PlayfieldToolType.Shovel;

            CurrentLevel?.Clear();
            CurrentLevel = level;

            _isFirstCellOpened = false;
            CurrentLevel.Create();

            ResumeInput();

            OnCreateLevel?.Invoke();
        }
        public void ClearCurrentLevel()
        {
            CurrentLevel.Clear();
            CurrentLevel = null;

            PauseInput();

            OnClearLevel?.Invoke();
        }
        public void ResetCurrentLevel()
        {
            ToolType = PlayfieldToolType.Shovel;

            CurrentLevel.Reset();
            _isFirstCellOpened = false;

            ResumeInput();
            OnClearLevel?.Invoke();
            OnCreateLevel?.Invoke();
            OnNumberOfUnsetFlagsChange?.Invoke(CurrentLevel.Config.NumberOfMines);
        }

        public void PauseInput()
        {
            _playerInput.IsActive = false;
        }
        public void ResumeInput()
        {
            _playerInput.IsActive = true;
        }
       
        public void EndPlay(bool isWin)
        {
            PauseInput();
            if (isWin)
            {
                OnEndPlay?.Invoke(EndPlayType.Win);
                _gameData.Money += CurrentLevel.Config.NumberOfMines;
            }
            else
                OnEndPlay?.Invoke(EndPlayType.Lose);
        }
        #endregion

        #region Other Methods
        private void InitPools()
        {
            var maxLevelSize = _levelConfigs
                .Max(levelConfig => levelConfig.Width * levelConfig.Height);

            _cellPool = new ObjectPool<Cell>(
                CreateCell,
                OnGetFromCellPool,
                OnReturnToCellPool,
                OnDestroyCell,
                defaultCapacity: maxLevelSize,
                maxSize: maxLevelSize);
            
            var cells = new Cell[maxLevelSize];
            for (int i = 0; i < maxLevelSize; i++)
                cells[i] = _cellPool.Get();
            for (int i = 0; i < maxLevelSize; i++)
                _cellPool.Release(cells[i]);
        }
        private void InitLevels()
        {
            Levels = new Level[_levelConfigs.Length];
            for (int i = 0; i < _levelConfigs.Length; i++)
                Levels[i] = CreateLevel(_levelConfigs[i]);
        }

        private void FirstTouchStarted(Vector2 screenPoint, float time)
        {
            _previousScreenPoint = screenPoint;
        }
        private void FirstTouchHolded(Vector2 screenPoint, float time)
        {
            switch (ToolType)
            {
                case PlayfieldToolType.Hand:
                    SetCameraPosition(screenPoint);
                    break;
            }
        }
        private void FirstTouchEnded(Vector2 screenPoint, float time)
        {
            if (_isTwoTouchStarted)
            {
                _isTwoTouchStarted = false;
                return;
            }

            var worldPoint = _playerCamera.ScreenToWorldPoint(screenPoint);
            switch (ToolType)
            {
                case PlayfieldToolType.Shovel:
                    if (time >= TOUCH_HOLD_TIME)
                        CurrentLevel.SwitchFlagInWorldPoint(worldPoint);
                    else
                        CurrentLevel.OpenCellInWorldPoint(worldPoint);
                    break;
                case PlayfieldToolType.Flag:
                    if (time >= TOUCH_HOLD_TIME)
                        CurrentLevel.OpenCellInWorldPoint(worldPoint);
                    else
                        CurrentLevel.SwitchFlagInWorldPoint(worldPoint);
                    break;
            }
        }

        private void SecondTouchStarted(Vector2 screenPoint, float time)
        {
            _previousScreenPoint = _playerInput.FirstTouchScreenPosition;
            _isTwoTouchStarted = true;
        }
        private void SecondTouchHolded(Vector2 screenPoint, float time)
        {
            SetCameraPosition(_playerInput.FirstTouchScreenPosition);
            SetCameraZoom();
        }
        private void SecondTouchEnded(Vector2 screenPoint, float time)
        {
            _previousDistance = Vector2.Distance(
                _playerInput.FirstTouchScreenPosition, 
                _playerInput.SecondTouchScreenPosition);
        }

        private void SetCameraZoom()
        {
            var distance = Vector2.Distance(
                _playerInput.FirstTouchScreenPosition, 
                _playerInput.SecondTouchScreenPosition);
            var deltaDistance = distance - _previousDistance;
            _previousDistance = distance;

            _playerCamera.Zoom -= deltaDistance * Time.deltaTime;
        }
        private void SetCameraPosition(Vector2 screenPoint)
        {
            var worldPoint = _playerCamera.ScreenToWorldPoint(screenPoint);
            var previousWorldPoint = _playerCamera.ScreenToWorldPoint(_previousScreenPoint);
            _previousScreenPoint = screenPoint;
            var deltaWorldPosition = previousWorldPoint - worldPoint;
            _playerCamera.Position += deltaWorldPosition;
        }

        private Level CreateLevel(LevelConfig config)
        {
            var level = new Level(config, _cellPool);
            level.OnCellOpen += CellOpened;
            level.OnNumberOfUnsetFlagsChange += (number) => 
                OnNumberOfUnsetFlagsChange?.Invoke(number);
            return level;
        }

        private void CellOpened(Cell cell)
        {
            if (!_isFirstCellOpened)
            {
                _isFirstCellOpened = true;
                OnFirstCellOpen?.Invoke();
            }

            if (cell.BodyType == CellBodyType.Mine)
            {
                EndPlay(false);
                CurrentLevel.OpenCellsWithMine();
            }
            else if (CurrentLevel.NumberOfNonMineCells == 0)
                EndPlay(true);
        }



        private Cell CreateCell()
        {
            var cell = new Cell(_config.CellConfig);
            return cell;
        }
        private void OnGetFromCellPool(Cell cell)
        {
            cell.IsActive = true;
        }
        private void OnReturnToCellPool(Cell cell)
        {
            cell.IsActive = false;
            cell.Reset();
        }
        private void OnDestroyCell(Cell cell)
        {
            cell.Destroy();
        }
        #endregion
    }
}
